package com.googlecode.animator.view.viewport
{
	import com.googlecode.animator.data.Notifications;
	import com.googlecode.data.ProjectConstants;
	import com.googlecode.data.cameras.ActualViewCameraVO;
	import com.googlecode.data.cameras.CameraVO;
	import com.googlecode.data.objects3d.CameraProxyVO;
	import com.googlecode.data.objects3d.Object3DVO;
	import com.googlecode.data.objects3d.TargetProxyVO;
	import com.googlecode.data.viewports.ViewportVO;
	import com.googlecode.animator.model.ProjectSettingsProxy;
	import com.googlecode.animator.services.AnimationTimer;
	import com.googlecode.utils.UniqueID;
	import com.googlecode.animator.view.viewselector.ViewSelector;
	import com.googlecode.animator.view.zoom.ZoomControl;
	
	import flash.events.Event;
	import flash.events.MouseEvent;
	
	import mx.events.SliderEvent;
	import mx.managers.ISystemManager;
	
	import org.papervision3d.core.math.Number3D;
	import org.papervision3d.objects.DisplayObject3D;
	import org.papervision3d.view.layer.ViewportLayer;
	import org.puremvc.as3.multicore.interfaces.INotification;
	import org.puremvc.as3.multicore.interfaces.IProxy;
	import org.puremvc.as3.multicore.patterns.mediator.Mediator;

	/**
	 * Ties in the Viewport component with the PureMVC framework. 
	 * @author USER
	 * 
	 */
	public class ViewportMediator extends Mediator
	{
		/**
		 * Produces a unique number to be appended to this mediator's name.
		 */
		public static var count:int = 0;
		
		/**
		 * Identifier. 
		 */
		public static const NAME:String = "ViewportMediator";

		/**
		 * References the animation timer. 
		 */
		private var _ticker:AnimationTimer;
		
		/**
		 *  Determines whether dragging is occurring or not.
		 */
		private var _dragging:Boolean;
		
		/**
		 * Stores the name of the currently selected object. 
		 */
		private var _selectedObjectName:String;
		
		/**
		 * Stores the name of the camera proxy. 
		 */
		protected var _cameraProxy:String;
		
		/**
		 * Stores the name of the target proxy. 
		 */
		protected var _targetProxy:String;
		
		/**
		 * Strores a reference to the 3D grid for this viewport.
		 */
		protected var _gridName:String;
		
		/**
		 * Constructor.
		 * @param viewComponent
		 * 
		 */
		public function ViewportMediator(viewComponent:Viewport)
		{
			var name:String = NAME + "_" + count;
			
			super(name, viewComponent);
						
			count ++;
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// PureMVC
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Triggered by PureMVC framework. 
		 * 
		 */
		override public function onRegister():void
		{
			// adds the 3D scene and renderer to the viewport
			setupViewport();
						
			viewRef.centreViewport();
			
			_ticker = AnimationTimer.getInstance();
			_ticker.addEventListener(AnimationTimer.EVENT, onTick);
			
			viewRef.addEventListener(Viewport.OBJECT3D_SELECTED, onObject3DSelected);
			viewRef.addEventListener(Viewport.OBJECT3D_RELEASED, onObject3DReleased);
			viewRef.addEventListener(Viewport.OBJECT3D_DELETE, onObject3DDelete);
			viewRef.addEventListener(Event.REMOVED, onViewportRemoved);
			
			var SM:ISystemManager = viewRef.systemManager;
			SM.addEventListener(MouseEvent.MOUSE_UP, onMouseUpAnywhere);
			
			var vVO:ViewportVO = new ViewportVO();
			vVO.viewportID = viewRef.id;
			vVO.currentCamera = viewRef.currentCamera;
			
			sendNotification(Notifications.ADD_VIEWPORT_MODEL, vVO);
		}
		
		/**
		 * Clean up. 
		 * 
		 */
		override public function onRemove():void
		{
			viewRef.removeEventListener(Viewport.OBJECT3D_SELECTED, onObject3DSelected);
			viewRef.removeEventListener(Viewport.OBJECT3D_RELEASED, onObject3DReleased);
			viewRef.removeEventListener(Viewport.OBJECT3D_DELETE, onObject3DDelete);
			
			var SM:ISystemManager = viewRef.systemManager;
			SM.removeEventListener(MouseEvent.MOUSE_UP, onMouseUpAnywhere);
			
			viewRef.destroy();
		}
		
		/**
		 * Lists notification of interests. 
		 * @return 
		 * 
		 */
		override public function listNotificationInterests():Array
		{
			var list:Array = super.listNotificationInterests();
			
			list.push(Notifications.SCENE_ADDED);
			list.push(Notifications.CAMERA_ADDED_MODEL);
			list.push(Notifications.CAMERA_POSITION_CHANGED_MODEL);
			list.push(Notifications.OBJECT3D_ADDED_MODEL);
			list.push(Notifications.OBJECT3D_MOVED);
			list.push(Notifications.OBJECT3D_POSITION_CHANGED_MODEL);
			list.push(Notifications.OBJECT3D_ROTATION_CHANGED_MODEL);
			list.push(Notifications.OBJECT3D_DELETED_MODEL);
			list.push(Notifications.OBJECT3D_DIMENSION_CHANGED_MODEL);
			list.push(Notifications.OBJECT3D_MATERIAL_UPDATED_MODEL);
			list.push(Notifications.PLANE_PROJECTION_CHANGED_MODEL);
			list.push(Notifications.ZOOM_CONTROL_CHANGED);
			list.push(Notifications.ZOOM_CONTROL_COMPLETED);
			//list.push(Notifications.DEFAULT_VIEWPORTS_ADDED);
			list.push(Notifications.VIEWPORT_ADDED_MODEL);
			list.push(Notifications.VIEWPORT_UPDATED_MODEL);
			list.push(Notifications.GRID_ADDED);
			list.push(Notifications.DEFAULT_CAMERAS_ADDED_MODEL);
			//list.push(Notifications.VIEWPORT_SETUP_COMPLETE);
			list.push(Notifications.TIMELINE_SCRUB_OBJECTS3D);
			list.push(Notifications.VIEW_SELECTOR_CHANGED);
			list.push(Notifications.PROJECT_IS_RESET);
			
			return list;
		}
		
		/**
		 * Handles notification of interests. 
		 * @param notification
		 * 
		 */
		override public function handleNotification(notification:INotification):void
		{
			super.handleNotification(notification);

			switch(notification.getName())
			{
				/* case Notifications.PROJECT_SCENE_ADDED:
				
					handleSceneAdded();
					
					break; */	
				case Notifications.CAMERA_ADDED_MODEL:
				
					handleCameraAdded(notification.getBody() as CameraVO);
					
					break; 
					
				case Notifications.CAMERA_POSITION_CHANGED_MODEL:
				
					handleCameraPositionChanged(notification.getBody() as String);
					
					break;
					
				case Notifications.OBJECT3D_ADDED_MODEL:
				
					handleObject3DAdded(notification.getBody() as Object3DVO);
					
					break;
				
				case Notifications.OBJECT3D_MOVED:
				
					handleObject3DMoved();
					
					break;
				case Notifications.OBJECT3D_POSITION_CHANGED_MODEL:
				
					handleObject3DPositionChanged();
					
					break;
					
				case Notifications.OBJECT3D_ROTATION_CHANGED_MODEL:
				
					handleObject3DRotationChanged();
					
					break;
				
				case Notifications.OBJECT3D_DELETED_MODEL:
				
					handleObject3DDeleted(notification.getBody() as String);
					
					break;
						
				case Notifications.OBJECT3D_DIMENSION_CHANGED_MODEL:
				
					handleObject3DDimensionsChanged();
					
					break;
					
				case Notifications.OBJECT3D_MATERIAL_UPDATED_MODEL:
				
					handleMaterialChanged();
					
					break;
					
				case Notifications.PLANE_PROJECTION_CHANGED_MODEL:
				
					handlePlaneProjectionChanged(notification.getBody() as Object3DVO);
					
					break;
					
				case Notifications.ZOOM_CONTROL_CHANGED:
				
					handleZoomControlChange(notification.getBody() as SliderEvent);
					
					break;
					
				case Notifications.ZOOM_CONTROL_COMPLETED:
				
					handleZoomControlComplete(notification.getBody() as SliderEvent);
					
					break;
				
				case Notifications.VIEWPORT_ADDED_MODEL:
				
					handleViewportAdded(notification.getBody() as ViewportVO);
					
					break; 
						
				 case Notifications.DEFAULT_VIEWPORTS_ADDED:
				
					//handleDefaultViewportsAdded();
					
					break; 
					
				case Notifications.VIEWPORT_UPDATED_MODEL:
				
					handleViewportUpdated(notification.getBody() as ViewportVO);
					
					break;
					
				case Notifications.GRID_ADDED:
				
					handleGridAdded();
					
					break;
					
				case Notifications.DEFAULT_CAMERAS_ADDED_MODEL:
				
					handleDefaultCamerasAdded();
					
					break; 

				case Notifications.TIMELINE_SCRUB_OBJECTS3D:
				
					handleScrubObjects3D();
					
					break;				
				
				case Notifications.VIEW_SELECTOR_CHANGED:
				
					handleViewSelectorChange(notification.getBody() as ViewSelector);
					
					break;
					
				case Notifications.PROJECT_IS_RESET:
				
					handleProjectIsReset();
					
					break;
			}
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// PureMVC: handlers
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Handles the addition of a new Camera3D. 
		 * @param camera
		 * 
		 */
		protected function handleCameraAdded(camera:CameraVO):void
		{	
			if(camera.name == viewRef.currentCamera)
			{	
				if(viewRef.id != ProjectConstants.VIEWPORT_1)
				{
					viewRef.label = camera.label;
				}
			}
		}   
		
		/**
		 * Handles the change in position of a 3D camera. 
		 * @param name
		 * 
		 */
		protected function handleCameraPositionChanged(name:String):void
		{
			var camera:CameraVO = settings.getCamera(name);

			if(camera.name == viewRef.currentCamera)
			{
				viewRef.updateViewportCamera(camera);
				viewRef.render();
			}
		}
		
		/**
		 * Handles the addition of a new DisplayObject3D. 
		 * @param object3D
		 * 
		 */
		protected function handleObject3DAdded(object3D:Object3DVO):void
		{
			if(object3D is CameraProxyVO)
			{
				_cameraProxy = object3D.name;
			}
			
			if(object3D is TargetProxyVO)
			{
				_targetProxy = object3D.name;
			} 
			
			viewRef.addObject(object3D);
			viewRef.render();
		}
		
		/**
		 * Handles the case of an Object3D having its position changed. 
		 * 
		 */
		protected function handleObject3DPositionChanged():void
		{
			viewRef.render();
		}
		
		/**
		 * Handles the case of an Object3D having its rotation changed. 
		 * 
		 */
		protected function handleObject3DRotationChanged():void
		{
			viewRef.render();
		}
		
		/**
		 * Handles the case of a user moving a 3d object in any viewport. 
		 * 
		 */
		protected function handleObject3DMoved():void
		{
			if(viewRef.currentCamera == null)
			{
				return;
			}
			
			var cameraProxy:DisplayObject3D = settings.scene.getChildByName(_cameraProxy);
			var targetProxy:DisplayObject3D = settings.scene.getChildByName(_targetProxy);
			
			var camera:CameraVO = settings.getCamera(viewRef.currentCamera).clone() as CameraVO;
			
			if(camera as ActualViewCameraVO)
			{
				if(cameraProxy != null && targetProxy != null)
				{
					camera.target = new Number3D();
					camera.target.x = targetProxy.x;
					camera.target.y = targetProxy.y;
					camera.target.z = targetProxy.z;
					camera.x = cameraProxy.x;
					camera.y = cameraProxy.y;
					camera.z = cameraProxy.z;

					viewRef.updateViewportCamera(camera);
				}
			}
			
			viewRef.render();
		}
		
		/**
		 * Handles a 3D object having its dimensions changed. 
		 * 
		 */
		protected function handleObject3DDimensionsChanged():void
		{
			viewRef.render();
		}
		
		/**
		 * Handles a 3D object being deleted. 
		 * 
		 */
		protected function handleObject3DDeleted(name:String):void
		{
			_dragging = false;
			_selectedObjectName = null;
			
			viewRef.dragObject = null;
			
			viewRef.render();
			
		}
		
		/**
		 * Handles a 3D object having its material changed. 
		 * 
		 */
		protected function handleMaterialChanged():void
		{
			viewRef.render();
		}
		
		/**
		 * Handles a Plane being chnaged from normal to "billboard" or vice versa. 
		 * 
		 */
		protected function handlePlaneProjectionChanged(VO:Object3DVO):void
		{
			viewRef.addObject(VO);
			viewRef.render();
		}
		
		/**
		 * Handles changes made to zoom settings for a viewport. 
		 * @param evt
		 * 
		 */
		protected function handleZoomControlChange(evt:SliderEvent):void
		{
			
			var zoomControl:ZoomControl = evt.target as ZoomControl;
			var viewportID:String = zoomControl.viewportID;
			var zoom:int = zoomControl.value * viewRef.zoomScale;

			if(viewportID == viewRef.id)
			{
				viewRef.zoom = zoom;
				viewRef.render();
			}
		}
				
		/**
		 * Handles the completion of the change of a zoom setting for a viewport. 
		 * 
		 */
		protected function handleZoomControlComplete(evt:SliderEvent):void
		{	
		
			var zoomControl:ZoomControl = evt.target as ZoomControl;

			if(viewRef.id != zoomControl.viewportID)
			{
				return;
				
			}
			
			var zoom:int = zoomControl.value * viewRef.zoomScale;
			
			var camera:CameraVO = new CameraVO();

			camera.name = viewRef.currentCamera;
			camera.zoom = zoom;
			
			sendNotification(Notifications.UPDATE_CAMERA_SETTINGS_MODEL, camera);
						
		}
		/**
		 * Handles changes to the ViewSelector component. 
		 * @param VO
		 * 
		 */
		protected function handleViewSelectorChange(vs:ViewSelector):void
		{
			var camera:CameraVO= vs.selectedItem as CameraVO;
			camera = settings.getCamera(camera.name);

			if(vs.viewportID == viewRef.id)
			{
				updateCamera(camera);
				
				// update label				
				if(viewRef.id != ProjectConstants.VIEWPORT_1)
				{
					viewRef.label = camera.label;
				}
				
				if(camera.gridOrientation != "notSet")
				{
					viewRef.removeViewportObject3D(_gridName);
					addGrid(camera.gridOrientation);
				}else
				{
					viewRef.removeViewportObject3D(_gridName);
					_gridName = null;
				}
				
				orient3DPlane(camera);
				viewRef.currentCamera = camera.name;
				viewRef.render();
				
				// send notification that current camera has changed
				var vVO:ViewportVO = new ViewportVO();
				vVO.viewportID = viewRef.id;
				vVO.currentCamera = camera.name;
				sendNotification(Notifications.VIEWPORT_UPDATED_MODEL, vVO);
			}
			
			
			
			setGridVisibilities();		
		}
		
		/**
		 * Handles when the timeline is controlling the view. 
		 * This function is not tied in with the PureMVC model. 
		 * 
		 */
		protected function handleScrubObjects3D():void
		{
			handleObject3DMoved();
		}
		
		protected function handleViewportAdded(vVO:ViewportVO):void
		{
			if(viewRef.id == vVO.viewportID)
			{	
				viewRef.currentCamera = vVO.currentCamera;
			  	
			  	if(settings.numViewports() == 4)
				{
					sendNotification(Notifications.DEFAULT_VIEWPORTS_ADDED);
				}  
			}
		} 
		
		/**
		 * Once all the viewports have been added it is safe to carry out
		 * operations that apply to all of the viewports.
		 * 
		 */
		/* protected function handleDefaultViewportsAdded():void
		{
			// set the current camera
			viewRef.currentCamera = settings.getViewport(viewRef.id).currentCamera;
			
			
		}  */ 
		
		protected function handleViewportUpdated(vVO:ViewportVO):void
		{
			if(viewRef.id == vVO.viewportID)
			{	
				viewRef.currentCamera = vVO.currentCamera;
			}
		}
		
		/**
		 * Once all the default cameras have been loaded, the 3D plane and grids can be set up. 
		 * 
		 */
		protected function handleDefaultCamerasAdded():void
		{
			var camera:CameraVO = settings.getCamera(viewRef.currentCamera);

			if(camera.gridOrientation != "notSet")
			{
				addGrid(camera.gridOrientation);
				//viewRef.orientGrid(camera.gridOrientation);
			}
			
			orient3DPlane(camera);
			
			viewRef.updateViewportCamera(camera);
			
			sendNotification(Notifications.GRID_ADDED);

		} 
		
		/**
		 * Each time a grid is added the visibilities of the grids need to be set. 
		 * 
		 */
		protected function handleGridAdded():void
		{
			setGridVisibilities();
		}
		
		/**
		 * Handles the restting of the project.
		 * @param viewports
		 * 
		 */
		protected function handleProjectIsReset():void
		{	
			_targetProxy = null;
			_cameraProxy = null;
			_selectedObjectName = null;
			_gridName = null;
			viewRef.render();
			_dragging = false;
		}
		
		/**
		 * Handles additional viewports being added to the UI. 
		 * @param viewports
		 * 
		 */
		/* protected function handleViewportAdded(viewports:Array):void
		{
			// not currently needed
		} */
		
		//////////////////////////////////////////////////////////////////////
		//
		// 3D object selection functions
		//
		//////////////////////////////////////////////////////////////////////
	
		/**
		 * Handles when the user releases a 3D object. 
		 * 
		 */
		protected function object3DReleased():void
		{
			_dragging = false;
			
			sendNotification(Notifications.MOUSE_UP_OBJECT3D, _selectedObjectName);
			
			viewRef.dragObject = null;
			_selectedObjectName = null;
		} 
		
		//////////////////////////////////////////////////////////////////////
		//
		// Event handlers
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Responds to any mouse up event. 
		 * @param evt
		 * 
		 */
		protected function onMouseUpAnywhere(evt:MouseEvent):void
		{	
			if(_selectedObjectName != null)
			{
				object3DReleased();
			}
		}
		
		/**
		 * Responds to the user clicking on a DisplayObject3D in one of the viewports. 
		 * @param name
		 * 
		 */
		protected function onObject3DSelected(evt:Event):void
		{
			var previous:String = settings.selectedObject3D;
			_selectedObjectName = viewRef.selectedObject3D;
			
			_dragging = true;
			
			if(_selectedObjectName != previous)
			{
				
				sendNotification(Notifications.OBJECT3D_SELECTED_MODEL, _selectedObjectName);
			}
	
		}
		
		/**
		 * Responds to the user clicking off a DisplayObject3D in one of the viewports. 
		 * @param name
		 * 
		 */
		protected function onObject3DReleased(evt:Event):void
		{
			object3DReleased();	
		}
		
		/**
		 * Responds to when a user attempts to delete an object. 
		 * @param evt
		 * 
		 */
		protected function onObject3DDelete(evt:Event):void
		{
			sendNotification(Notifications.DELETE_OBJECT3D_MODEL, viewRef.selectedObject3D);	
		}
		
		/**
		 * Handles timer events. 
		 * @param evt
		 * 
		 */
		protected function onTick(evt:Event):void
		{
			if(_dragging && _selectedObjectName != null)
			{
				viewRef.render();
				// let the other viewports know to render their views
				sendNotification(Notifications.OBJECT3D_MOVED);
			}
			
		}
		
		/**
		 * Removes this mediator from the PureMVC framework, if the component is removed. 
		 * @param evt
		 * 
		 */
		protected function onViewportRemoved(evt:Event):void
		{
			facade.removeMediator(getMediatorName());
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Viewport functions
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Adds the scene and renderer to the viewport. Sets up the viewport.
		 * @param proxy
		 * 
		 */
		protected function setupViewport():void
		{
			viewRef.scene = settings.scene;
			viewRef.renderer = settings.renderer;
			viewRef.setupViewport();
		}
		
		/**
		 * Creates and adds a 3D grid to the viewport. 
		 * 
		 */
		protected function addGrid(orientation:String):void
		{
			var uid:UniqueID = new UniqueID();
			var grid:Grid3D = new Grid3D(ProjectConstants.DEFAULT_GRID_WIDTH, 20);
			grid.name = "grid_" + uid.generateID();
			_gridName = grid.name;

			viewRef.addViewportObject3D(grid);
			
			orientGrid(grid, orientation);
			
			//setGridVisibilities();
		}
		
		/**
		 * Orients grid. 
		 * 
		 */
		public function orientGrid(grid:Grid3D, orientation:String):void
		{
			switch(orientation)
			{
				case "xz":
					
					// do nothing, default orientation of grid.
					
					break;
					
				case "xy":
				
					grid.rotationX = 90;
					
					break;
					
				case "zy":
					
					grid.rotationZ = 90;
					
					break;
			}
		}
		
		/**
		 * Sets the grid visibilities correctly for each viewport. 
		 * 
		 */
		public function setGridVisibilities():void
		{
			var vpl:ViewportLayer;
			
			for each(var child:DisplayObject3D in settings.scene.children)
			{
				if(child as Grid3D)
				{	
					vpl = viewRef.viewport.getChildLayer(child as DisplayObject3D);
					vpl.mouseEnabled = false;
					vpl.visible = false;

					if(_gridName != null)
					{
						if(child.name == _gridName)
						{
							vpl.visible = true;
						}
					}
				}
			}
			
			viewRef.render();
		} 
		
		/**
		 * Orients the 3D plane, which is used in the capture of mouse clicks in 3D space. 
		 * @param camera
		 * 
		 */
		protected function orient3DPlane(camera:CameraVO):void
		{
			if(camera.plane3DOrientation != "notSet")
			{
				viewRef.plane3DOrientation = camera.plane3DOrientation;
			}
		}
		
		
		//////////////////////////////////////////////////////////////////////
		//
		// Camera functions
		//
		//////////////////////////////////////////////////////////////////////
			
		/**
		 * Updates 3D camera for the viewport. 
		 * @param camera
		 * 
		 */
		protected function updateCamera(camera:CameraVO):void
		{
			viewRef.updateViewportCamera(camera);
			
			viewRef.currentCamera = camera.name;
			viewRef.zoomScale = camera.zoomScale;
			
			// we need to make sure we store a reference specifying what camera 
			// is currently associated with this viewport
			/* var vVO:ViewportVO = new ViewportVO();
			vVO.currentCamera = camera.name;
			vVO.viewportID = viewRef.id;
			
			sendNotification(Notifications.VIEWPORT_UPDATED, vVO); */				
			
		}
		
		//////////////////////////////////////////////////////////////////////
		//
		// Utility functions
		//
		//////////////////////////////////////////////////////////////////////
		
		/**
		 * Returns typed reference to the model. 
		 * @return 
		 * 
		 */
		protected function get settings():ProjectSettingsProxy
		{
			var proxy:IProxy = facade.retrieveProxy(ProjectSettingsProxy.NAME);
			
			return proxy as ProjectSettingsProxy;
		}
		
		/**
		 * Returns a typed reference to the Viewport component. 
		 * @return 
		 * 
		 */
		protected function get viewRef():Viewport
		{
			return viewComponent as Viewport;
		}
	}
}